package gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;

import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SpringLayout;

import urbanparks.Job;

/**
 * The panel that visually displays the calendar with the
 * jobs on it.
 * 
 * @author Michelle Miller
 * @version Winter 2012
 *
 */
@SuppressWarnings("serial")
public class CalendarPanel extends JPanel {
  
  /** 
   * The calendar object used by this panel to keep track
   * of the currently showing month.
   */
  private final Calendar my_calendar =
      Calendar.getInstance();

  /** A reference to the controller. */
  private final UrbanParksManagerFrame my_manager;
  
  /**
   * The JPanel containing the Label displaying the month
   * and year.
   */
  private final JPanel my_month_year_panel = new JPanel();
  
  /** 
   * The label that displays the month and year above the
   * calendar. 
   */
  private final JLabel my_month_year_label = new JLabel();
  
  /**
   * The panel that holds the days of the month.
   */
  private JPanel my_calendar_panel;
  

  /**
   * The constructor for the CalendarPanel class.
   * 
   * @param the_manager A reference to the controller.
   */
  public CalendarPanel(
      final UrbanParksManagerFrame the_manager) {
    super(new BorderLayout());
    my_manager = the_manager;
    updateMonthYearPanel();
    updateCalendarPanel();
    my_month_year_panel.add(my_month_year_label);
    
    this.add(my_month_year_panel, BorderLayout.NORTH);
  }

  /**
   * Private helper method that updates the month
   * year label with the month and year currently
   * represented by my_calendar. 
   */
  private void updateMonthYearPanel() {
    String month;
    final int monthNum = my_calendar.get(Calendar.MONTH);
    switch (monthNum) {
      case Calendar.JANUARY :
        month = "January";
        break;
      case Calendar.FEBRUARY : 
        month = "February";
        break;
      case Calendar.MARCH : 
        month = "March";
        break;
      case Calendar.APRIL : 
        month = "April";
        break;
      case Calendar.MAY : 
        month = "May";
        break;
      case Calendar.JUNE : 
        month = "June";
        break;
      case Calendar.JULY : 
        month = "July";
        break;
      case Calendar.AUGUST : 
        month = "August";
        break;
      case Calendar.SEPTEMBER : 
        month = "September";
        break;
      case Calendar.OCTOBER : 
        month = "October";
        break;
      case Calendar.NOVEMBER : 
        month = "November";
        break;
      default : 
        month = "December";
        break;
    }
    final int year = my_calendar.get(Calendar.YEAR);
    my_month_year_label.setText(month + " " + year);
    my_month_year_panel.repaint();
  }

  /**
   * Private helper method to update the CalendarPanel.
   */
  private void updateCalendarPanel() {
    if (my_calendar_panel != null) {
      this.remove(my_calendar_panel);
    }
    my_calendar_panel = new JPanel(new BorderLayout());

    my_calendar_panel.add(getDayLabelsPanel(), BorderLayout.NORTH);
    my_calendar_panel.add(getMonthPanel(), BorderLayout.CENTER);
    
    my_calendar_panel.repaint();
    this.add(my_calendar_panel, BorderLayout.CENTER);
  }

  /**
   * Private helper method that returns a JPanel with a
   * SpringLayout that displays all of the days of the
   * month.
   * 
   * @return A JPanel containing all of the days of the
   *  month with jobs displayed on their respective day.
   */
  private Component getMonthPanel() {
    final JPanel monthPanel =
        new JPanel(new SpringLayout());
    final List<List<Job>> jobsByDay = getJobsbyDay();
    final int topBlanks = fillTopBlanks(monthPanel);
    for (int i = 1; i <= my_calendar.getActualMaximum(
        Calendar.DAY_OF_MONTH); i++) {
      monthPanel.add(getDayPanel(jobsByDay.get(i), i));
    }
    int rows;
    int endBlanks = my_calendar.getActualMaximum(
        Calendar.DAY_OF_MONTH) + topBlanks;
    if (endBlanks > 35) {
      rows = 6;
      endBlanks = 42 - endBlanks;
    } else {
      rows = 5;
      endBlanks = 35 - endBlanks;
    }
    addBlank(endBlanks, monthPanel);
    SpringUtilities.makeGrid(
        monthPanel, rows, 7, //rows, cols
        5, 5, //initialX, initialY
        5, 5);//xPad, yPad
    monthPanel.setOpaque(true);
    return monthPanel;
  }

  /**
   * Private Helper method that gets a spring panel
   * containing labels for the days of the week.
   * 
   * @return The JPanel containing all of the labels for
   *  the days of the week.
   */
  private JPanel getDayLabelsPanel() {
    final JPanel result = new JPanel(new SpringLayout());
    final String[] days = {"Sunday", "Monday", "Tuesday",
        "Wednesday", "Thursday", "Friday", "Saturday"};
    for (String day : days) {
      final JPanel tmp = new JPanel();
      tmp.add(new JLabel(day));
      result.add(tmp);
    }
    SpringUtilities.makeCompactGrid(
        result, 1, 7, //rows, cols
        5, 5, //initialX, initialY
        5, 5);//xPad, yPad
    result.setOpaque(true);
    return result;
    
    
  }

  /**
   * Private helper method that creates a JPanel that
   * contains the displayed information for a single day
   * on the month panel.
   * 
   * @param the_jobs The list of jobs occurring on that
   *  day.
   * @param the_day The numerical day of the month
   *  displayed in this panel.
   * @return The JPanel containing the day of the month
   *  label and a dropdown menu that lists the jobs on
   *  that day.
   */
  private JPanel getDayPanel(final List<Job> the_jobs,
      final int the_day) {
    final JPanel dayPanel = new JPanel(new BorderLayout());
    dayPanel.setBackground(Color.WHITE);
    dayPanel.add(new JLabel(Integer.toString(the_day)),
        BorderLayout.NORTH);
    
    final JPanel jobPanel = new JPanel();
    
    if (the_jobs.size() > 0) {
      jobPanel.add(getDayCB(the_jobs));
    }
    
    jobPanel.setBackground(Color.WHITE);
    dayPanel.add(jobPanel);
    return dayPanel;
    
  }

  /**
   * Private helper method that returns a JComboBox that
   * displays a list of the jobs occurring on a day.
   * Selecting a job from the list will cause that job's
   * JobInformationForm to be displayed.
   * 
   * @param the_jobs The list of jobs occurring on that
   *  day.
   * @return A JComboBox that displays a list of the jobs
   *  occurring on a day. Selecting a Job in the list
   *  causes that job's JobInformationForm to be displayed.
   */
  private JComboBox getDayCB(final List<Job> the_jobs) {
    Vector<Job> jobModel = new Vector<Job>();
    for (Job j : the_jobs) {
      jobModel.addElement(j);
    }

    final JComboBox comboBox = new JComboBox(jobModel);
    comboBox.setSelectedIndex(-1);
    comboBox.addItemListener(new ItemListener() {

      public void itemStateChanged(ItemEvent the_event) {
        if (the_event.getStateChange() ==
            ItemEvent.SELECTED) {
          Job j = (Job) the_event.getItem();
          
          final JobInformationForm overlay =
              new JobInformationForm(j, my_manager);
          overlay.setVisible(true);
          comboBox.setSelectedIndex(-1);
        }
      }
    });
    return comboBox;
  }

  /**
   * Private helper method that fills in the blank day
   * panels so that the first day of the month lines up
   * with it's day of the week.
   * 
   * @param the_panel The panel to add the blanks to.
   * 
   * @return The number of blank panels inserted.
   */
  private int fillTopBlanks(final JPanel the_panel) {
    int numAdded;
    my_calendar.set(Calendar.DAY_OF_MONTH, 1);
    final int day = my_calendar.get(Calendar.DAY_OF_WEEK);
    switch (day) {
      case Calendar.MONDAY : //add one blanks
        numAdded = 1;
        break;
      case Calendar.TUESDAY :
        numAdded = 2;
        break;
      case Calendar.WEDNESDAY :
        numAdded = 3;
        break;
      case Calendar.THURSDAY :
        numAdded = 4;
        break;
      case Calendar.FRIDAY :
        numAdded = 5;
        break;
      case Calendar.SATURDAY :
        numAdded = 6;
        break;
      default :
        numAdded = 0;
        break;
      //if its sunday then don't add any blank boxes before. 
    }
    addBlank(numAdded, the_panel);
    return numAdded;
  }
  
  /**
   * Private helper method that creates the blank day
   * panels and adds them to the JPanel provided.
   * 
   * @param the_count The number of blank panels to add.
   * @param the_panel The panel to add the blanks to.
   */
  private void addBlank(final int the_count,
      final JPanel the_panel) {
    for (int i = 1; i <= the_count; i++) {
      final JPanel result = new JPanel();
      the_panel.add(result);
    }
    
  }

  /**
   * Private helper method that gets the jobs that are in
   * the current month and day and puts them into a list
   * where the index that corresponds to the day of the
   * month contains a list of all the jobs occurring on
   * that day.
   * 
   * @return The indexed list of lists of jobs. Indexes
   *  in the outer list correspond to the day the List
   *  of jobs starts on.
   */
  private List<List<Job>> getJobsbyDay() {
    final List<List<Job>> jobsByDay =
        new ArrayList<List<Job>>();
    for (int i = 0; i <= my_calendar.getActualMaximum(
        Calendar.DAY_OF_MONTH); i++) {
      jobsByDay.add(i, new ArrayList<Job>());
    }
    
    final List<Job> jobsInThisMonthYear =
        my_manager.getJobSet().getJobsByMonthYear(
            my_calendar.get(Calendar.MONTH),
        my_calendar.get(Calendar.YEAR));
    
    if (jobsInThisMonthYear != null) {
      for (Job j : jobsInThisMonthYear) {
        List<Job> jobsOnDay = jobsByDay.get(
            j.getJobDuration().getDay());
        jobsOnDay.add(j);
        jobsByDay.set(j.getJobDuration().getDay(), jobsOnDay);
      }
    }
    return jobsByDay;
  }

  /**
   * This method increments the calendar to the next month.
   */
  public void nextMonth() {
    if (my_calendar.get(Calendar.MONTH) == 
        my_calendar.getActualMaximum(Calendar.MONTH)) {
      final int month = my_calendar.getActualMinimum(Calendar.MONTH);
      final int year = my_calendar.get(Calendar.YEAR) + 1;
      my_calendar.set(Calendar.MONTH, month);
      my_calendar.set(Calendar.YEAR, year);
    } else {
      final int month = my_calendar.get(Calendar.MONTH) + 1;
      my_calendar.set(Calendar.MONTH, month);
    }
    updateCalendar();
  }

  /**
   * This method decrements the calendar to the next month.
   */
  public void previousMonth() {
    if (my_calendar.get(Calendar.MONTH) == 
        my_calendar.getActualMinimum(Calendar.MONTH)) {
      final int month = my_calendar.getActualMaximum(Calendar.MONTH);
      final int year = my_calendar.get(Calendar.YEAR) - 1;
      my_calendar.set(Calendar.MONTH, month);
      my_calendar.set(Calendar.YEAR, year);
    } else {
      final int month = my_calendar.get(Calendar.MONTH) - 1;
      my_calendar.set(Calendar.MONTH, month);
    }
    updateCalendar();
  }
  
  /**
   * This method updates the CalendarPanel.
   */
  public void updateCalendar() {
    updateCalendarPanel();
    updateMonthYearPanel();
  }

}
